{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 一、锯齿波的傅里叶展开"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# inline 模式下，matplotlib 绘制的图会直接输出到 notebook 内\n",
    "# 但是 inline 模式有很多缺陷（不支持 animation，不能交互）\n",
    "%matplotlib inline\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "PI2 = 2*np.pi\n",
    "PI3 = 3*np.pi\n",
    "PI4 = 4*np.pi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.1 锯齿波函数的定义与图形\n",
    "\n",
    "$$\n",
    "\\begin{equation}\n",
    "        f(x)=\n",
    "        \\begin{cases}\n",
    "            \\frac{1}{2}(\\pi - x) & 0 < x \\leq 2 \\pi \\\\\n",
    "            f(x + 2 \\pi) & \\text{otherwise}\n",
    "        \\end{cases}\n",
    "    \\end{equation}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x11c7ab850>]"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 600x400 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 锯齿波图形\n",
    "\n",
    "def f(x):  # 函数定义\n",
    "    if x <= 0:\n",
    "        return f(x + PI2)\n",
    "    elif x > 2*np.pi:\n",
    "        return f(x - PI2)\n",
    "    else:\n",
    "        return 1/2 * (np.pi - x)\n",
    "\n",
    "X = np.arange(-PI3, PI3, 0.02)  # x 范围\n",
    "Y = list(map(f, X))             # 计算对应的 y 值\n",
    "\n",
    "fig, axes = plt.subplots(dpi=100)\n",
    "axes.grid()\n",
    "axes.plot(X, Y)    # 绘图"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.2 傅里叶展开\n",
    "\n",
    "这是一个奇函数，展开为正弦级数为 $\\sum_{n=1}^{\\infty} \\frac{\\sin nx}{n}$，\n",
    "下面开始绘制级数叠加过程图"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 这里使用矩阵方法，同时计算 n 项的值。因为这种方式要比单独计算每一项要快。\n",
    "\n",
    "n_max = 300   # 展开到第 n 项\n",
    "N = np.arange(1,n_max)\n",
    "X = np.arange(-PI3, PI3, 0.001)  # x 范围\n",
    "\n",
    "NX = np.outer(N, X)   # 计算 N 和 X 的外积，所得矩阵的每一行，即为对应 n 值的 nx 向量。\n",
    "\n",
    "NY = np.array(np.sin(NX) / np.matrix(N).transpose())   # 计算对应的 Y（矩阵），n 应被对应的 sin(nx) 所除，所以 N 需要转置\n",
    "\n",
    "Serise = np.cumsum(NY, axis=0)  # 计算级数，不是原地计算，因此不会影响到 NY"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.3 动画"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n    if (typeof WebSocket !== 'undefined') {\n        return WebSocket;\n    } else if (typeof MozWebSocket !== 'undefined') {\n        return MozWebSocket;\n    } else {\n        alert(\n            'Your browser does not have WebSocket support. ' +\n                'Please try Chrome, Safari or Firefox ≥ 6. ' +\n                'Firefox 4 and 5 are also supported but you ' +\n                'have to enable WebSockets in about:config.'\n        );\n    }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n    this.id = figure_id;\n\n    this.ws = websocket;\n\n    this.supports_binary = this.ws.binaryType !== undefined;\n\n    if (!this.supports_binary) {\n        var warnings = document.getElementById('mpl-warnings');\n        if (warnings) {\n            warnings.style.display = 'block';\n            warnings.textContent =\n                'This browser does not support binary websocket messages. ' +\n                'Performance may be slow.';\n        }\n    }\n\n    this.imageObj = new Image();\n\n    this.context = undefined;\n    this.message = undefined;\n    this.canvas = undefined;\n    this.rubberband_canvas = undefined;\n    this.rubberband_context = undefined;\n    this.format_dropdown = undefined;\n\n    this.image_mode = 'full';\n\n    this.root = document.createElement('div');\n    this.root.setAttribute('style', 'display: inline-block');\n    this._root_extra_style(this.root);\n\n    parent_element.appendChild(this.root);\n\n    this._init_header(this);\n    this._init_canvas(this);\n    this._init_toolbar(this);\n\n    var fig = this;\n\n    this.waiting = false;\n\n    this.ws.onopen = function () {\n        fig.send_message('supports_binary', { value: fig.supports_binary });\n        fig.send_message('send_image_mode', {});\n        if (fig.ratio !== 1) {\n            fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n        }\n        fig.send_message('refresh', {});\n    };\n\n    this.imageObj.onload = function () {\n        if (fig.image_mode === 'full') {\n            // Full images could contain transparency (where diff images\n            // almost always do), so we need to clear the canvas so that\n            // there is no ghosting.\n            fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n        }\n        fig.context.drawImage(fig.imageObj, 0, 0);\n    };\n\n    this.imageObj.onunload = function () {\n        fig.ws.close();\n    };\n\n    this.ws.onmessage = this._make_on_message_function(this);\n\n    this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n    var titlebar = document.createElement('div');\n    titlebar.classList =\n        'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n    var titletext = document.createElement('div');\n    titletext.classList = 'ui-dialog-title';\n    titletext.setAttribute(\n        'style',\n        'width: 100%; text-align: center; padding: 3px;'\n    );\n    titlebar.appendChild(titletext);\n    this.root.appendChild(titlebar);\n    this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n    var fig = this;\n\n    var canvas_div = (this.canvas_div = document.createElement('div'));\n    canvas_div.setAttribute(\n        'style',\n        'border: 1px solid #ddd;' +\n            'box-sizing: content-box;' +\n            'clear: both;' +\n            'min-height: 1px;' +\n            'min-width: 1px;' +\n            'outline: 0;' +\n            'overflow: hidden;' +\n            'position: relative;' +\n            'resize: both;'\n    );\n\n    function on_keyboard_event_closure(name) {\n        return function (event) {\n            return fig.key_event(event, name);\n        };\n    }\n\n    canvas_div.addEventListener(\n        'keydown',\n        on_keyboard_event_closure('key_press')\n    );\n    canvas_div.addEventListener(\n        'keyup',\n        on_keyboard_event_closure('key_release')\n    );\n\n    this._canvas_extra_style(canvas_div);\n    this.root.appendChild(canvas_div);\n\n    var canvas = (this.canvas = document.createElement('canvas'));\n    canvas.classList.add('mpl-canvas');\n    canvas.setAttribute('style', 'box-sizing: content-box;');\n\n    this.context = canvas.getContext('2d');\n\n    var backingStore =\n        this.context.backingStorePixelRatio ||\n        this.context.webkitBackingStorePixelRatio ||\n        this.context.mozBackingStorePixelRatio ||\n        this.context.msBackingStorePixelRatio ||\n        this.context.oBackingStorePixelRatio ||\n        this.context.backingStorePixelRatio ||\n        1;\n\n    this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n    var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n        'canvas'\n    ));\n    rubberband_canvas.setAttribute(\n        'style',\n        'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n    );\n\n    // Apply a ponyfill if ResizeObserver is not implemented by browser.\n    if (this.ResizeObserver === undefined) {\n        if (window.ResizeObserver !== undefined) {\n            this.ResizeObserver = window.ResizeObserver;\n        } else {\n            var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n            this.ResizeObserver = obs.ResizeObserver;\n        }\n    }\n\n    this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n        var nentries = entries.length;\n        for (var i = 0; i < nentries; i++) {\n            var entry = entries[i];\n            var width, height;\n            if (entry.contentBoxSize) {\n                if (entry.contentBoxSize instanceof Array) {\n                    // Chrome 84 implements new version of spec.\n                    width = entry.contentBoxSize[0].inlineSize;\n                    height = entry.contentBoxSize[0].blockSize;\n                } else {\n                    // Firefox implements old version of spec.\n                    width = entry.contentBoxSize.inlineSize;\n                    height = entry.contentBoxSize.blockSize;\n                }\n            } else {\n                // Chrome <84 implements even older version of spec.\n                width = entry.contentRect.width;\n                height = entry.contentRect.height;\n            }\n\n            // Keep the size of the canvas and rubber band canvas in sync with\n            // the canvas container.\n            if (entry.devicePixelContentBoxSize) {\n                // Chrome 84 implements new version of spec.\n                canvas.setAttribute(\n                    'width',\n                    entry.devicePixelContentBoxSize[0].inlineSize\n                );\n                canvas.setAttribute(\n                    'height',\n                    entry.devicePixelContentBoxSize[0].blockSize\n                );\n            } else {\n                canvas.setAttribute('width', width * fig.ratio);\n                canvas.setAttribute('height', height * fig.ratio);\n            }\n            canvas.setAttribute(\n                'style',\n                'width: ' + width + 'px; height: ' + height + 'px;'\n            );\n\n            rubberband_canvas.setAttribute('width', width);\n            rubberband_canvas.setAttribute('height', height);\n\n            // And update the size in Python. We ignore the initial 0/0 size\n            // that occurs as the element is placed into the DOM, which should\n            // otherwise not happen due to the minimum size styling.\n            if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n                fig.request_resize(width, height);\n            }\n        }\n    });\n    this.resizeObserverInstance.observe(canvas_div);\n\n    function on_mouse_event_closure(name) {\n        return function (event) {\n            return fig.mouse_event(event, name);\n        };\n    }\n\n    rubberband_canvas.addEventListener(\n        'mousedown',\n        on_mouse_event_closure('button_press')\n    );\n    rubberband_canvas.addEventListener(\n        'mouseup',\n        on_mouse_event_closure('button_release')\n    );\n    rubberband_canvas.addEventListener(\n        'dblclick',\n        on_mouse_event_closure('dblclick')\n    );\n    // Throttle sequential mouse events to 1 every 20ms.\n    rubberband_canvas.addEventListener(\n        'mousemove',\n        on_mouse_event_closure('motion_notify')\n    );\n\n    rubberband_canvas.addEventListener(\n        'mouseenter',\n        on_mouse_event_closure('figure_enter')\n    );\n    rubberband_canvas.addEventListener(\n        'mouseleave',\n        on_mouse_event_closure('figure_leave')\n    );\n\n    canvas_div.addEventListener('wheel', function (event) {\n        if (event.deltaY < 0) {\n            event.step = 1;\n        } else {\n            event.step = -1;\n        }\n        on_mouse_event_closure('scroll')(event);\n    });\n\n    canvas_div.appendChild(canvas);\n    canvas_div.appendChild(rubberband_canvas);\n\n    this.rubberband_context = rubberband_canvas.getContext('2d');\n    this.rubberband_context.strokeStyle = '#000000';\n\n    this._resize_canvas = function (width, height, forward) {\n        if (forward) {\n            canvas_div.style.width = width + 'px';\n            canvas_div.style.height = height + 'px';\n        }\n    };\n\n    // Disable right mouse context menu.\n    this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n        event.preventDefault();\n        return false;\n    });\n\n    function set_focus() {\n        canvas.focus();\n        canvas_div.focus();\n    }\n\n    window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n    var fig = this;\n\n    var toolbar = document.createElement('div');\n    toolbar.classList = 'mpl-toolbar';\n    this.root.appendChild(toolbar);\n\n    function on_click_closure(name) {\n        return function (_event) {\n            return fig.toolbar_button_onclick(name);\n        };\n    }\n\n    function on_mouseover_closure(tooltip) {\n        return function (event) {\n            if (!event.currentTarget.disabled) {\n                return fig.toolbar_button_onmouseover(tooltip);\n            }\n        };\n    }\n\n    fig.buttons = {};\n    var buttonGroup = document.createElement('div');\n    buttonGroup.classList = 'mpl-button-group';\n    for (var toolbar_ind in mpl.toolbar_items) {\n        var name = mpl.toolbar_items[toolbar_ind][0];\n        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n        var image = mpl.toolbar_items[toolbar_ind][2];\n        var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n        if (!name) {\n            /* Instead of a spacer, we start a new button group. */\n            if (buttonGroup.hasChildNodes()) {\n                toolbar.appendChild(buttonGroup);\n            }\n            buttonGroup = document.createElement('div');\n            buttonGroup.classList = 'mpl-button-group';\n            continue;\n        }\n\n        var button = (fig.buttons[name] = document.createElement('button'));\n        button.classList = 'mpl-widget';\n        button.setAttribute('role', 'button');\n        button.setAttribute('aria-disabled', 'false');\n        button.addEventListener('click', on_click_closure(method_name));\n        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n        var icon_img = document.createElement('img');\n        icon_img.src = '_images/' + image + '.png';\n        icon_img.srcset = '_images/' + image + '_large.png 2x';\n        icon_img.alt = tooltip;\n        button.appendChild(icon_img);\n\n        buttonGroup.appendChild(button);\n    }\n\n    if (buttonGroup.hasChildNodes()) {\n        toolbar.appendChild(buttonGroup);\n    }\n\n    var fmt_picker = document.createElement('select');\n    fmt_picker.classList = 'mpl-widget';\n    toolbar.appendChild(fmt_picker);\n    this.format_dropdown = fmt_picker;\n\n    for (var ind in mpl.extensions) {\n        var fmt = mpl.extensions[ind];\n        var option = document.createElement('option');\n        option.selected = fmt === mpl.default_extension;\n        option.innerHTML = fmt;\n        fmt_picker.appendChild(option);\n    }\n\n    var status_bar = document.createElement('span');\n    status_bar.classList = 'mpl-message';\n    toolbar.appendChild(status_bar);\n    this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n    // which will in turn request a refresh of the image.\n    this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n    properties['type'] = type;\n    properties['figure_id'] = this.id;\n    this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n    if (!this.waiting) {\n        this.waiting = true;\n        this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n    }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n    var format_dropdown = fig.format_dropdown;\n    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n    fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n    var size = msg['size'];\n    if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n        fig._resize_canvas(size[0], size[1], msg['forward']);\n        fig.send_message('refresh', {});\n    }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n    var x0 = msg['x0'] / fig.ratio;\n    var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n    var x1 = msg['x1'] / fig.ratio;\n    var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n    x0 = Math.floor(x0) + 0.5;\n    y0 = Math.floor(y0) + 0.5;\n    x1 = Math.floor(x1) + 0.5;\n    y1 = Math.floor(y1) + 0.5;\n    var min_x = Math.min(x0, x1);\n    var min_y = Math.min(y0, y1);\n    var width = Math.abs(x1 - x0);\n    var height = Math.abs(y1 - y0);\n\n    fig.rubberband_context.clearRect(\n        0,\n        0,\n        fig.canvas.width / fig.ratio,\n        fig.canvas.height / fig.ratio\n    );\n\n    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n    // Updates the figure title.\n    fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n    var cursor = msg['cursor'];\n    switch (cursor) {\n        case 0:\n            cursor = 'pointer';\n            break;\n        case 1:\n            cursor = 'default';\n            break;\n        case 2:\n            cursor = 'crosshair';\n            break;\n        case 3:\n            cursor = 'move';\n            break;\n    }\n    fig.rubberband_canvas.style.cursor = cursor;\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n    fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n    // Request the server to send over a new figure.\n    fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n    fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n    for (var key in msg) {\n        if (!(key in fig.buttons)) {\n            continue;\n        }\n        fig.buttons[key].disabled = !msg[key];\n        fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n    }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n    if (msg['mode'] === 'PAN') {\n        fig.buttons['Pan'].classList.add('active');\n        fig.buttons['Zoom'].classList.remove('active');\n    } else if (msg['mode'] === 'ZOOM') {\n        fig.buttons['Pan'].classList.remove('active');\n        fig.buttons['Zoom'].classList.add('active');\n    } else {\n        fig.buttons['Pan'].classList.remove('active');\n        fig.buttons['Zoom'].classList.remove('active');\n    }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n    // Called whenever the canvas gets updated.\n    this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n    return function socket_on_message(evt) {\n        if (evt.data instanceof Blob) {\n            var img = evt.data;\n            if (img.type !== 'image/png') {\n                /* FIXME: We get \"Resource interpreted as Image but\n                 * transferred with MIME type text/plain:\" errors on\n                 * Chrome.  But how to set the MIME type?  It doesn't seem\n                 * to be part of the websocket stream */\n                img.type = 'image/png';\n            }\n\n            /* Free the memory for the previous frames */\n            if (fig.imageObj.src) {\n                (window.URL || window.webkitURL).revokeObjectURL(\n                    fig.imageObj.src\n                );\n            }\n\n            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n                img\n            );\n            fig.updated_canvas_event();\n            fig.waiting = false;\n            return;\n        } else if (\n            typeof evt.data === 'string' &&\n            evt.data.slice(0, 21) === 'data:image/png;base64'\n        ) {\n            fig.imageObj.src = evt.data;\n            fig.updated_canvas_event();\n            fig.waiting = false;\n            return;\n        }\n\n        var msg = JSON.parse(evt.data);\n        var msg_type = msg['type'];\n\n        // Call the  \"handle_{type}\" callback, which takes\n        // the figure and JSON message as its only arguments.\n        try {\n            var callback = fig['handle_' + msg_type];\n        } catch (e) {\n            console.log(\n                \"No handler for the '\" + msg_type + \"' message type: \",\n                msg\n            );\n            return;\n        }\n\n        if (callback) {\n            try {\n                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n                callback(fig, msg);\n            } catch (e) {\n                console.log(\n                    \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n                    e,\n                    e.stack,\n                    msg\n                );\n            }\n        }\n    };\n};\n\n// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\nmpl.findpos = function (e) {\n    //this section is from http://www.quirksmode.org/js/events_properties.html\n    var targ;\n    if (!e) {\n        e = window.event;\n    }\n    if (e.target) {\n        targ = e.target;\n    } else if (e.srcElement) {\n        targ = e.srcElement;\n    }\n    if (targ.nodeType === 3) {\n        // defeat Safari bug\n        targ = targ.parentNode;\n    }\n\n    // pageX,Y are the mouse positions relative to the document\n    var boundingRect = targ.getBoundingClientRect();\n    var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n    var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n\n    return { x: x, y: y };\n};\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * http://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n    return Object.keys(original).reduce(function (obj, key) {\n        if (typeof original[key] !== 'object') {\n            obj[key] = original[key];\n        }\n        return obj;\n    }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n    var canvas_pos = mpl.findpos(event);\n\n    if (name === 'button_press') {\n        this.canvas.focus();\n        this.canvas_div.focus();\n    }\n\n    var x = canvas_pos.x * this.ratio;\n    var y = canvas_pos.y * this.ratio;\n\n    this.send_message(name, {\n        x: x,\n        y: y,\n        button: event.button,\n        step: event.step,\n        guiEvent: simpleKeys(event),\n    });\n\n    /* This prevents the web browser from automatically changing to\n     * the text insertion cursor when the button is pressed.  We want\n     * to control all of the cursor setting manually through the\n     * 'cursor' event from matplotlib */\n    event.preventDefault();\n    return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n    // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n    // Prevent repeat events\n    if (name === 'key_press') {\n        if (event.key === this._key) {\n            return;\n        } else {\n            this._key = event.key;\n        }\n    }\n    if (name === 'key_release') {\n        this._key = null;\n    }\n\n    var value = '';\n    if (event.ctrlKey && event.key !== 'Control') {\n        value += 'ctrl+';\n    }\n    else if (event.altKey && event.key !== 'Alt') {\n        value += 'alt+';\n    }\n    else if (event.shiftKey && event.key !== 'Shift') {\n        value += 'shift+';\n    }\n\n    value += 'k' + event.key;\n\n    this._key_event_extra(event, name);\n\n    this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n    return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n    if (name === 'download') {\n        this.handle_save(this, null);\n    } else {\n        this.send_message('toolbar_button', { name: name });\n    }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n    this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n    // Create a \"websocket\"-like object which calls the given IPython comm\n    // object with the appropriate methods. Currently this is a non binary\n    // socket, so there is still some room for performance tuning.\n    var ws = {};\n\n    ws.binaryType = comm.kernel.ws.binaryType;\n    ws.readyState = comm.kernel.ws.readyState;\n    function updateReadyState(_event) {\n        if (comm.kernel.ws) {\n            ws.readyState = comm.kernel.ws.readyState;\n        } else {\n            ws.readyState = 3; // Closed state.\n        }\n    }\n    comm.kernel.ws.addEventListener('open', updateReadyState);\n    comm.kernel.ws.addEventListener('close', updateReadyState);\n    comm.kernel.ws.addEventListener('error', updateReadyState);\n\n    ws.close = function () {\n        comm.close();\n    };\n    ws.send = function (m) {\n        //console.log('sending', m);\n        comm.send(m);\n    };\n    // Register the callback with on_msg.\n    comm.on_msg(function (msg) {\n        //console.log('receiving', msg['content']['data'], msg);\n        var data = msg['content']['data'];\n        if (data['blob'] !== undefined) {\n            data = {\n                data: new Blob(msg['buffers'], { type: data['blob'] }),\n            };\n        }\n        // Pass the mpl event to the overridden (by mpl) onmessage function.\n        ws.onmessage(data);\n    });\n    return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n    // This is the function which gets called when the mpl process\n    // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n    var id = msg.content.data.id;\n    // Get hold of the div created by the display call when the Comm\n    // socket was opened in Python.\n    var element = document.getElementById(id);\n    var ws_proxy = comm_websocket_adapter(comm);\n\n    function ondownload(figure, _format) {\n        window.open(figure.canvas.toDataURL());\n    }\n\n    var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n    // web socket which is closed, not our websocket->open comm proxy.\n    ws_proxy.onopen();\n\n    fig.parent_element = element;\n    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n    if (!fig.cell_info) {\n        console.error('Failed to find cell for figure', id, fig);\n        return;\n    }\n    fig.cell_info[0].output_area.element.on(\n        'cleared',\n        { fig: fig },\n        fig._remove_fig_handler\n    );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n    var width = fig.canvas.width / fig.ratio;\n    fig.cell_info[0].output_area.element.off(\n        'cleared',\n        fig._remove_fig_handler\n    );\n    fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n    // Update the output cell to use the data from the current canvas.\n    fig.push_to_output();\n    var dataURL = fig.canvas.toDataURL();\n    // Re-enable the keyboard manager in IPython - without this line, in FF,\n    // the notebook keyboard shortcuts fail.\n    IPython.keyboard_manager.enable();\n    fig.parent_element.innerHTML =\n        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n    fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n    fig.send_message('closing', msg);\n    // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n    // Turn the data on the canvas into data in the output cell.\n    var width = this.canvas.width / this.ratio;\n    var dataURL = this.canvas.toDataURL();\n    this.cell_info[1]['text/html'] =\n        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n    // Tell IPython that the notebook contents must change.\n    IPython.notebook.set_dirty(true);\n    this.send_message('ack', {});\n    var fig = this;\n    // Wait a second, then push the new image to the DOM so\n    // that it is saved nicely (might be nice to debounce this).\n    setTimeout(function () {\n        fig.push_to_output();\n    }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n    var fig = this;\n\n    var toolbar = document.createElement('div');\n    toolbar.classList = 'btn-toolbar';\n    this.root.appendChild(toolbar);\n\n    function on_click_closure(name) {\n        return function (_event) {\n            return fig.toolbar_button_onclick(name);\n        };\n    }\n\n    function on_mouseover_closure(tooltip) {\n        return function (event) {\n            if (!event.currentTarget.disabled) {\n                return fig.toolbar_button_onmouseover(tooltip);\n            }\n        };\n    }\n\n    fig.buttons = {};\n    var buttonGroup = document.createElement('div');\n    buttonGroup.classList = 'btn-group';\n    var button;\n    for (var toolbar_ind in mpl.toolbar_items) {\n        var name = mpl.toolbar_items[toolbar_ind][0];\n        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n        var image = mpl.toolbar_items[toolbar_ind][2];\n        var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n        if (!name) {\n            /* Instead of a spacer, we start a new button group. */\n            if (buttonGroup.hasChildNodes()) {\n                toolbar.appendChild(buttonGroup);\n            }\n            buttonGroup = document.createElement('div');\n            buttonGroup.classList = 'btn-group';\n            continue;\n        }\n\n        button = fig.buttons[name] = document.createElement('button');\n        button.classList = 'btn btn-default';\n        button.href = '#';\n        button.title = name;\n        button.innerHTML = '<i class=\"fa ' + image + ' fa-lg\"></i>';\n        button.addEventListener('click', on_click_closure(method_name));\n        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n        buttonGroup.appendChild(button);\n    }\n\n    if (buttonGroup.hasChildNodes()) {\n        toolbar.appendChild(buttonGroup);\n    }\n\n    // Add the status bar.\n    var status_bar = document.createElement('span');\n    status_bar.classList = 'mpl-message pull-right';\n    toolbar.appendChild(status_bar);\n    this.message = status_bar;\n\n    // Add the close button to the window.\n    var buttongrp = document.createElement('div');\n    buttongrp.classList = 'btn-group inline pull-right';\n    button = document.createElement('button');\n    button.classList = 'btn btn-mini btn-primary';\n    button.href = '#';\n    button.title = 'Stop Interaction';\n    button.innerHTML = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\n    button.addEventListener('click', function (_evt) {\n        fig.handle_close(fig, {});\n    });\n    button.addEventListener(\n        'mouseover',\n        on_mouseover_closure('Stop Interaction')\n    );\n    buttongrp.appendChild(button);\n    var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n    titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n    var fig = event.data.fig;\n    if (event.target !== this) {\n        // Ignore bubbled events from children.\n        return;\n    }\n    fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n    el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n    // this is important to make the div 'focusable\n    el.setAttribute('tabindex', 0);\n    // reach out to IPython and tell the keyboard manager to turn it's self\n    // off when our div gets focus\n\n    // location in version 3\n    if (IPython.notebook.keyboard_manager) {\n        IPython.notebook.keyboard_manager.register_events(el);\n    } else {\n        // location in version 2\n        IPython.keyboard_manager.register_events(el);\n    }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n    var manager = IPython.notebook.keyboard_manager;\n    if (!manager) {\n        manager = IPython.keyboard_manager;\n    }\n\n    // Check for shift+enter\n    if (event.shiftKey && event.which === 13) {\n        this.canvas_div.blur();\n        // select the cell after this one\n        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n        IPython.notebook.select(index + 1);\n    }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n    fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n    // Return the cell and output element which can be found *uniquely* in the notebook.\n    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n    // IPython event is triggered only after the cells have been serialised, which for\n    // our purposes (turning an active figure into a static one), is too late.\n    var cells = IPython.notebook.get_cells();\n    var ncells = cells.length;\n    for (var i = 0; i < ncells; i++) {\n        var cell = cells[i];\n        if (cell.cell_type === 'code') {\n            for (var j = 0; j < cell.output_area.outputs.length; j++) {\n                var data = cell.output_area.outputs[j];\n                if (data.data) {\n                    // IPython >= 3 moved mimebundle to data attribute of output\n                    data = data.data;\n                }\n                if (data['text/html'] === html_output) {\n                    return [cell, data, j];\n                }\n            }\n        }\n    }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n    IPython.notebook.kernel.comm_manager.register_target(\n        'matplotlib',\n        mpl.mpl_figure_comm\n    );\n}\n",
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div id='4d0bbee5-59f6-4a2f-82cf-aeed8233ca31'></div>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 1. 使用 matplotlib 的 animation。略麻烦，但是交互性高。\n",
    "%matplotlib notebook\n",
    "\n",
    "from matplotlib.lines import Line2D\n",
    "import matplotlib.animation as animation\n",
    "\n",
    "fig, (ax1, ax2) = plt.subplots(1, 2, dpi=100)\n",
    "ax1.grid()\n",
    "ax2.grid()\n",
    "\n",
    "\n",
    "def animate(Y):\n",
    "    ax1.plot(X,Y)\n",
    "    return ax2.plot(X,Y, \"black\")\n",
    "\n",
    "\n",
    "ani = animation.FuncAnimation(\n",
    "    fig, animate, frames=Serise, interval=80, blit=True)\n",
    "\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "43ef14d4506c4e06941b21b6b8a3c413",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "interactive(children=(IntSlider(value=50, description='i', min=1), Output()), _dom_classes=('widget-interact',…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<function __main__.update_lines(i)>"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 2. 使用 jupyterlab 的 ipywidgets 插件进行交互\n",
    "import ipywidgets as widgets\n",
    "\n",
    "%matplotlib inline\n",
    "\n",
    "def update_lines(i):\n",
    "    # inline 模式下，fig好像是一经显示就不能修改的，因此每次都要创建新的\n",
    "    fig, axes = plt.subplots(dpi=150)\n",
    "    axes.grid()\n",
    "    axes.plot(X, Serise[i], 'red')\n",
    "    \n",
    "widgets.interact(update_lines, i=(1,100,1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
